package com.jl.filter;

import com.jl.ReflectUtils;
import com.jl.Tuple;
import com.jl.filter.annotate.Filter;
import com.jl.set.list.JArrayList;
import com.jl.set.list.JList;
import lombok.Builder;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;

import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

/**
 * 过滤器
 */
@Slf4j
@RequiredArgsConstructor
public class FilterImpl implements javax.servlet.Filter {

    private final ApplicationContext applicationContext;

    private JList<FilterParam> list;

    @Data
    @Builder
    public static class FilterParam {

        private Object bean;

        private Method method;

        private Object[] methodParams;

        private List<String> excludesList;

        private String error;

        private int order;

    }

    /**
     * 初始化
     */
    @Override
    public void init(FilterConfig filterConfig) {
        JList<FilterParam> jList = new JArrayList<>();
        String[] beans = applicationContext.getBeanDefinitionNames();
        for (String bean : beans) {
            Object beanObj;
            try {
                beanObj = applicationContext.getBean(bean);
            } catch (Exception e) {
                continue;
            }
            List<Tuple.Tuple2<Method, Filter>> methods = ReflectUtils.MethodReflect.getMethod(beanObj.getClass(), Filter.class);
            for (Tuple.Tuple2<Method, Filter> methodObj : methods) {
                Method method = methodObj.getV1();
                Filter filter = methodObj.getV2();
                FilterParam filterParam = FilterParam.builder()
                        .bean(beanObj)
                        .method(method)
                        .methodParams(ReflectUtils.MethodReflect.checkParamType(method.getParameters(), filter.values()))
                        .order(filter.order())
                        .excludesList(Arrays.asList(filter.excludes()))
                        .error(filter.error())
                        .build();
                jList.add(filterParam);
            }
        }
        list = jList.asc(FilterParam::getOrder);
    }

    /**
     * 执行
     */
    @Override
    @SneakyThrows
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        for (FilterParam filterParam : list) {
            List<String> excludesList = filterParam.getExcludesList();
            if (excludesList.contains(request.getRequestURI())) {
                continue;
            }
            Method method = filterParam.getMethod();
            Object invoke;
            try {
                invoke = method.invoke(filterParam.getBean(), filterParam.getMethodParams());
            } catch (Exception e) {
                log.error("过滤器执行异常：{}", e);
                return;
            }
            if (invoke != null && invoke instanceof Boolean) {
                if (!Boolean.parseBoolean(invoke.toString())) {
                    throw new ExceptionInfo(filterParam.getError());
                }
            }
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

    /**
     * 销毁
     */
    @Override
    public void destroy() {

    }

    @Bean
    public FilterRegistrationBean registFilter() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(this);
        registration.addUrlPatterns("/*");
        registration.setName("commFilter");
        registration.setOrder(0);
        return registration;
    }

    public static class ExceptionInfo extends RuntimeException {

        public ExceptionInfo(String message) {
            super(message);
        }

    }
}
